import { Injectable } from '@angular/core';
import { Observable, Observer } from 'rxjs';
import { fromPromise } from 'rxjs/observable/fromPromise';
import { fromEvent } from 'rxjs/observable/fromEvent';
import { switchMap, map, catchError } from 'rxjs/operators';
import { of } from 'rxjs/observable/of';
import * as ShapefileUtil from 'shapefile';
import * as JSZip from 'jszip';

import GPX from 'ol/format/gpx';
import GeoJSON from 'ol/format/geojson';
import IGC from 'ol/format/igc';
import KML from 'ol/format/kml';
import TopoJSON from 'ol/format/topojson';

export interface Shapefile {
  dbf?: File,
  prj?: File,
  sbn?: File,
  sbx?: File,
  shp: File,
  shx?: File
}

@Injectable()
export class ReadFilesService {

  private formatConstructors: any[] = [GPX, GeoJSON, IGC, KML, TopoJSON];
  private projection: 'EPSG:4326' | 'EPSG:3857' = 'EPSG:3857';

  constructor() { 
  }

  /**
   * @private
   * @param file File
   */
  readZip(file: File): Observable<any> {
    const accepts: string[] = [ '.cpg', '.dbf', '.prj', '.sbn', '.sbx', '.shp', '.shp.xml', '.shx' ];
    return fromPromise(JSZip.loadAsync(file))
      .pipe(
        switchMap((zip: any) => {
          const files = zip.files;
          const shapefiles = Object.keys(files)
            .reduce(( shapefile, key ) => {
              const extension: string = this.getFileExtension(files[key], accepts);
              if(extension) {
                shapefile = { ...shapefile, [extension]: files[key] };
              }
              return shapefile;
            }, {});
          return of(shapefiles);
        })
      )
  }

  serializeFile(file: any, type: string): Observable<any> {
    return fromPromise(file.async(type));
  }

  extractFileContent(files: { [key: string]: any }): Observable<{ [key: string]: any }> {
    const dbf = files['.dbf'];
    const prj = files['.prj'];
    const shp = files['.shp'];
    return Observable.create((observer: Observer<any>) => {
      const handleError = (code: number, type: string, msg: string, des?: any) => {
        observer.error({ code, type, msg, des });
        observer.complete();
      }
      let result: { [key: string]: any } = {};
      if(shp) {
        this.serializeFile(shp, 'arraybuffer').subscribe((content) => {
          result = { ...result, shp: content };
          // this.serializeFile(prj, 'text').subscribe((content) => {
          //   result = { ...result, prj: content };
            if(dbf) {
              this.serializeFile(dbf, 'arraybuffer').subscribe((content) => {
                result = { ...result, dbf: content };
                observer.next(result);
                observer.complete();
              }, (error) => handleError(500, 'Parsing Error', '文件解析失败。', error))
            } else {
              observer.next(result);
              observer.complete();
            }
          // }, (error) => handleError(500, 'Parsing Error', '文件解析失败。', error));
        }, (error) => handleError(500, 'Parsing Error', '文件解析失败。', error));
      } else {
        handleError(404, 'Error', '在Zip文件中未找到.shp文件，请提供至少包含以上文件的Zip文件。');
      }
    });
  }

  handleFiles(files: { [key: string]: any }): Observable<any> {
    return this.extractFileContent(files).pipe(
      switchMap(result => {
        return fromPromise(ShapefileUtil.read(result.shp, result.dbf, { encoding: 'utf-8' })).pipe(
          map(geojson => {
            return { geojson, proj: result.prj };
          })
        );
      })
    );
  }

  /**
   * @public
   * @param file File
   * @returns Observable<any[]>
   */
  readShapefileAsFeatures(file: File): Observable<any> {
    return this.readZip(file).pipe(
      switchMap(files => {
        return this.handleFiles(files).pipe(
          map(result => {
            return (new GeoJSON()).readFeatures(result.geojson, {
              dataProjection: 'EPSG:4326',
              featureProjection: 'EPSG:3857'
            });
          })
        );
      })
    );
  }

  readFilesAsFeatures(files: File[]): Observable < any[] > {
    let counter: number = 0;
    let features: any = [];
    return Observable.create((observer: Observer < any[] > ) => {
      for (let i = 0; i < files.length; i++) {
        const file = files[i];
        const reader = new FileReader();
        reader.addEventListener('load', (event: ProgressEvent) => {
          const result: any[] = this.handleResult(file, event);
          if (result && result.length > 0) {
            features = [...features, ...result];
          }
          counter++;
          if (counter === files.length) {
            observer.next(features);
            observer.complete();
          }
        });
        reader.readAsText(file);
      }
    });
  }

  private getFileExtension(file: any, accepts: string | string[]): string {
    if (file && accepts) {
      const acceptedFilesArray = Array.isArray(accepts) ? accepts : accepts.split(',');
      const fileName = '' + file.name;

      return acceptedFilesArray.find(type => {
        const validType = type.trim();
        return fileName.toLowerCase().indexOf(validType.toLowerCase(), fileName.toLowerCase().length - validType.toLowerCase().length) !== -1;
      });
    }
    return null;
  }

  private handleResult(file: File, event: ProgressEvent): any[] {
    const fileReader: FileReader = event.target as FileReader;
    const result = fileReader.result;
    let features: any[] = [];
    for (let i = 0; i < this.formatConstructors.length; i++) {
      const formatConstructor = this.formatConstructors[i];
      const format = new formatConstructor();
      features = this.tryReadFeatures(format, result, {
        dataProjection: 'EPSG:4326',
        featureProjection: 'EPSG:3857'
      }) || null;
      if (features && features.length > 0) {
        break;
      }
    }
    return features;
  }

  private tryReadFeatures(format: any, text: any, options: any): any[] {
    try {
      return format.readFeatures(text, options);
    } catch (e) {
      return null;
    }
  }

}
